package org.nicocube.airain.re;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.nicocube.airain.domain.client.character.Action;
import org.nicocube.airain.domain.client.character.ActionType;
import org.nicocube.airain.domain.client.character.GameCharacter;
import org.nicocube.airain.domain.client.data.StorageException;
import org.nicocube.airain.domain.client.gamedate.GameDateManager;
import org.nicocube.airain.domain.client.report.ActionReport;
import org.nicocube.airain.domain.server.data.ContainerProviderImpl;
import org.nicocube.airain.domain.server.data.OrderedStorage;
import org.nicocube.airain.domain.server.data.Storage;
import org.nicocube.airain.domain.server.data.StorageProvider;
import org.nicocube.airain.re.proc.EatingProcessor;
import org.nicocube.airain.re.proc.HuntingProcessor;
import org.nicocube.airain.re.proc.Processor;
import org.nicocube.airain.re.proc.ProcessorFactory;
import org.nicocube.airain.re.proc.SleepingProcessor;
import org.nicocube.airain.re.proc.WeaponryProcessor;
import org.nicocube.airain.utils.config.Config;
import org.nicocube.airain.utils.config.ConfigProvider;
import org.nicocube.airain.utils.server.Server;
import org.nicocube.airain.utils.telnet.Command;
import org.nicocube.airain.utils.telnet.CommandDescription;
import org.nicocube.airain.utils.telnet.EndingCommand;
import org.nicocube.airain.utils.telnet.TelnetServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 
 * Rule Engine is the program that process periodically the rules of airain Game for each player
 * 
 * @author nico
 * @version 0.1
 */
public class RuleEngine implements Server {
	private static final Logger log = LoggerFactory.getLogger(RuleEngine.class);

	private boolean stop = false;

	private final StorageProvider storageProvider;
	
	private ProcessorFactory processorFactory;
	private ScheduledExecutorService scheduler;

	// port this into each GameCharacter ? GameZone ?
	private GameDateManager dateManager;

	private String name;

	private long tic;

	public RuleEngine(Config engineConfig,StorageProvider storageProvider) throws SecurityException,
			NoSuchMethodException, StorageException {
		Map<ActionType, Class<? extends Processor>> actionProcessor = new HashMap<ActionType, Class<? extends Processor>>();
		actionProcessor.put(ActionType.Eating, EatingProcessor.class);
		actionProcessor.put(ActionType.Sleeping, SleepingProcessor.class);
		actionProcessor.put(ActionType.Hunting, HuntingProcessor.class);
		actionProcessor.put(ActionType.Weaponry, WeaponryProcessor.class);

		processorFactory = new ProcessorFactory(actionProcessor);
		scheduler = Executors.newScheduledThreadPool(1);
		
		this.storageProvider = storageProvider;
		
		name = engineConfig.get("name");
		tic = engineConfig.getInt("tic.duration");
	}

	@Override
	public String getName() {
		return name;
	}

	/**
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		Config engineConfig = ConfigProvider.inst().provide("/etc/airain/re.config");
		Config storageConfig = ConfigProvider.inst().provide("/etc/airain/storage.config");
		
		RuleEngine re = new RuleEngine(engineConfig,new StorageProvider(new ContainerProviderImpl(storageConfig)));

		TelnetServer ts = new TelnetServer(engineConfig
				.getInt(TelnetServer.TelnetPortProperty), re);
		ts.launch();

		re.process();
	}

	/**
	 * @throws InterruptedException
	 * @throws StorageException 
	 * @throws StorageException
	 * 
	 */
	protected void process() throws InterruptedException, StorageException {

		dateManager = storageProvider.getStorage(GameDateManager.class)
				.retrieveOne();
		
		synchronized (this) {
			scheduler.schedule(new Runnable() {

				@Override
				public void run() {
					try {
						Storage<GameDateManager> sto = storageProvider
								.getStorage(GameDateManager.class);

						OrderedStorage<GameCharacter> gcStorage = storageProvider
								.getStorage(GameCharacter.class);

						OrderedStorage<Action> actionStorage = storageProvider.getStorage(Action.class);

						Storage<ActionReport> reportStorage = storageProvider.getStorage(ActionReport.class);
						
						sto.refresh(dateManager);

						dateManager.incCurrent();

						Reactor reactor = new Reactor(dateManager,
								processorFactory, gcStorage, actionStorage, reportStorage);

						reactor.process();

						sto.save(dateManager).commit();
						gcStorage.commit();
					} catch (StorageException e) {
						log.error("Error while launching ", e);
					}
				}
			}, tic, TimeUnit.SECONDS);
			try {
				while (!stop) {
					// wait forever to be notified
					this.wait(Long.MAX_VALUE);
				}
			} catch (Exception e) {
				log.error("Error while waiting...", e);
			}
		}
	}

	public ProcessorFactory getProcessorFactory() {
		return processorFactory;
	}

	/**
	 * closes this server.
	 */
	@Command("stop")
	@EndingCommand
	@CommandDescription("shutdown the current RuleEngine")
	public void close() {
		synchronized (this) {
			stop = true;
			this.notify();
		}
	}

}
